/*
 * Copyright 2017 - 2025 the original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see [https://www.gnu.org/licenses/]
 */

package infra.aop.framework.autoproxy;

import org.aopalliance.aop.Advice;
import org.jspecify.annotations.Nullable;

import java.io.Serial;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import infra.aop.Advisor;
import infra.aop.AopInfrastructureBean;
import infra.aop.Pointcut;
import infra.aop.TargetSource;
import infra.aop.framework.Advised;
import infra.aop.framework.ProxyCreator;
import infra.aop.framework.ProxyFactory;
import infra.aop.framework.ProxyFactoryBean;
import infra.aop.framework.ProxyProcessorSupport;
import infra.aop.framework.adapter.AdvisorAdapterRegistry;
import infra.aop.framework.adapter.DefaultAdvisorAdapterRegistry;
import infra.aop.target.EmptyTargetSource;
import infra.aop.target.SingletonTargetSource;
import infra.beans.BeansException;
import infra.beans.factory.BeanFactory;
import infra.beans.factory.BeanFactoryAware;
import infra.beans.factory.FactoryBean;
import infra.beans.factory.InitializationBeanPostProcessor;
import infra.beans.factory.config.AutowireCapableBeanFactory;
import infra.beans.factory.config.BeanPostProcessor;
import infra.beans.factory.config.ConfigurableBeanFactory;
import infra.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import infra.core.SmartClassLoader;
import infra.lang.Assert;
import infra.logging.Logger;
import infra.logging.LoggerFactory;
import infra.util.ClassUtils;
import infra.util.CollectionUtils;
import infra.util.StringUtils;

/**
 * {@link BeanPostProcessor} implementation
 * that wraps each eligible bean with an AOP proxy, delegating to specified interceptors
 * before invoking the bean itself.
 *
 * <p>This class distinguishes between "common" interceptors: shared for all proxies it
 * creates, and "specific" interceptors: unique per bean instance. There need not be any
 * common interceptors. If there are, they are set using the interceptorNames property.
 * As with {@link ProxyFactoryBean}, interceptors names
 * in the current factory are used rather than bean references to allow correct handling
 * of prototype advisors and interceptors: for example, to support stateful mixins.
 * Any advice type is supported for {@link #setInterceptorNames "interceptorNames"} entries.
 *
 * <p>Such auto-proxying is particularly useful if there's a large number of beans that
 * need to be wrapped with similar proxies, i.e. delegating to the same interceptors.
 * Instead of x repetitive proxy definitions for x target beans, you can register
 * one single such post processor with the bean factory to achieve the same effect.
 *
 * <p>Subclasses can apply any strategy to decide if a bean is to be proxied, e.g. by type,
 * by name, by definition details, etc. They can also return additional interceptors that
 * should just be applied to the specific bean instance. A simple concrete implementation is
 * {@link BeanNameAutoProxyCreator}, identifying the beans to be proxied via given names.
 *
 * <p>Any number of {@link TargetSourceCreator} implementations can be used to create
 * a custom target source: for example, to pool prototype objects. Auto-proxying will
 * occur even if there is no advice, as long as a TargetSourceCreator specifies a custom
 * {@link TargetSource}. If there are no TargetSourceCreators set,
 * or if none matches, a {@link SingletonTargetSource}
 * will be used by default to wrap the target bean instance.
 *
 * @author Juergen Hoeller
 * @author Rod Johnson
 * @author Rob Harrop
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
 * @see #setInterceptorNames
 * @see #getAdvicesAndAdvisorsForBean
 * @see BeanNameAutoProxyCreator
 * @see DefaultAdvisorAutoProxyCreator
 * @since 3.0 2021/2/1 21:31
 */
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements AopInfrastructureBean,
        ProxyCreator, BeanFactoryAware, InitializationBeanPostProcessor, SmartInstantiationAwareBeanPostProcessor {

  @Serial
  private static final long serialVersionUID = 1L;

  private static final Logger log = LoggerFactory.getLogger(AbstractAutoProxyCreator.class);

  /**
   * Convenience constant for subclasses: Return value for "do not proxy".
   *
   * @see #getAdvicesAndAdvisorsForBean
   */
  protected static final Object @Nullable [] DO_NOT_PROXY = null;

  /**
   * Convenience constant for subclasses: Return value for
   * "proxy without additional interceptors, just the common ones".
   *
   * @see #getAdvicesAndAdvisorsForBean
   */
  protected static final Object[] PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS = new Object[0];

  @Nullable
  private BeanFactory beanFactory;

  private transient TargetSourceCreator @Nullable [] customTargetSourceCreators;

  /** Default is no common interceptors. */
  private String[] interceptorNames = new String[0];

  private boolean applyCommonInterceptorsFirst = true;

  /** Default is global AdvisorAdapterRegistry. */
  private AdvisorAdapterRegistry advisorAdapterRegistry = DefaultAdvisorAdapterRegistry.getInstance();

  private final Set<String> targetSourcedBeans = ConcurrentHashMap.newKeySet(16);

  private final ConcurrentHashMap<Object, Object> earlyBeanReferences = new ConcurrentHashMap<>(16);

  private final ConcurrentHashMap<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);

  private final ConcurrentHashMap<Object, Class<?>> proxyTypes = new ConcurrentHashMap<>(16);

  /**
   * Set custom {@code TargetSourceCreators} to be applied in this order.
   * If the list is empty, or they all return null, a {@link SingletonTargetSource}
   * will be created for each bean.
   * <p>Note that TargetSourceCreators will kick in even for target beans
   * where no advices or advisors have been found. If a {@code TargetSourceCreator}
   * returns a {@link TargetSource} for a specific bean, that bean will be proxied
   * in any case.
   * <p>{@code TargetSourceCreators} can only be invoked if this post processor is used
   * in a {@link BeanFactory} and its {@link BeanFactoryAware} callback is triggered.
   *
   * @param customTargetSourceCreators the list of {@code TargetSourceCreators}.
   * Ordering is significant: The {@code TargetSource} returned from the first matching
   * {@code TargetSourceCreator} (that is, the first that returns non-null) will be used.
   */
  public void setCustomTargetSourceCreators(TargetSourceCreator... customTargetSourceCreators) {
    this.customTargetSourceCreators = customTargetSourceCreators;
  }

  /**
   * Specify the {@link AdvisorAdapterRegistry} to use.
   * <p>Default is the global {@link AdvisorAdapterRegistry}.
   *
   * @see DefaultAdvisorAdapterRegistry
   */
  public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) {
    Assert.notNull(advisorAdapterRegistry, "AdvisorAdapterRegistry is required");
    this.advisorAdapterRegistry = advisorAdapterRegistry;
  }

  /**
   * Set the common interceptors. These must be bean names in the current factory.
   * They can be of any advice or advisor type supports.
   * <p>If this property isn't set, there will be zero common interceptors.
   * This is perfectly valid, if "specific" interceptors such as matching
   * Advisors are all we want.
   */
  public void setInterceptorNames(String... interceptorNames) {
    this.interceptorNames = interceptorNames;
  }

  /**
   * Set whether the common interceptors should be applied before bean-specific ones.
   * Default is "true"; else, bean-specific interceptors will get applied first.
   */
  public void setApplyCommonInterceptorsFirst(boolean applyCommonInterceptorsFirst) {
    this.applyCommonInterceptorsFirst = applyCommonInterceptorsFirst;
  }

  @Override
  public void setBeanFactory(BeanFactory beanFactory) {
    this.beanFactory = beanFactory;
    AutoProxyUtils.applyDefaultProxyConfig(this, beanFactory);
  }

  /**
   * Return the owning {@link BeanFactory}.
   * May be {@code null}, as this post-processor doesn't need to belong to a bean factory.
   */
  @Nullable
  public BeanFactory getBeanFactory() {
    return this.beanFactory;
  }

  @Nullable
  @Override
  public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
    Object cacheKey = getCacheKey(beanClass, beanName);

    if (StringUtils.isEmpty(beanName) || !this.targetSourcedBeans.contains(beanName)) {
      if (this.advisedBeans.containsKey(cacheKey)) {
        return null;
      }
      if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return null;
      }
    }

    // Create proxy here if we have a custom TargetSource.
    // Suppresses unnecessary default instantiation of the target bean:
    // The TargetSource will handle target instances in a custom fashion.
    TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
    if (targetSource != null) {
      if (StringUtils.isNotEmpty(beanName)) {
        this.targetSourcedBeans.add(beanName);
      }
      Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
      Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
    }

    return null;
  }

  @Nullable
  protected TargetSource getCustomTargetSource(Class<?> beanClass, String beanName) {
    // We can't create fancy target sources for directly registered singletons.
    if (this.customTargetSourceCreators != null) {
      for (TargetSourceCreator creator : this.customTargetSourceCreators) {
        TargetSource source = creator.getTargetSource(beanClass, beanName);
        if (source != null) {
          // Found a matching TargetSource.
          if (log.isTraceEnabled()) {
            log.trace("TargetSourceCreator [{}] found custom TargetSource for bean with name '{}'",
                    creator, beanName);
          }
          return source;
        }
      }
    }
    // No custom TargetSource found.
    return null;
  }

  @Override
  @Nullable
  public Class<?> predictBeanType(Class<?> beanClass, String beanName) {
    if (this.proxyTypes.isEmpty()) {
      return null;
    }
    Object cacheKey = getCacheKey(beanClass, beanName);
    return this.proxyTypes.get(cacheKey);
  }

  @Override
  public Class<?> determineBeanType(Class<?> beanClass, String beanName) {
    Object cacheKey = getCacheKey(beanClass, beanName);
    Class<?> proxyType = this.proxyTypes.get(cacheKey);
    if (proxyType == null) {
      TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
      if (targetSource != null) {
        if (StringUtils.isNotEmpty(beanName)) {
          this.targetSourcedBeans.add(beanName);
        }
      }
      else {
        targetSource = EmptyTargetSource.forClass(beanClass);
      }
      Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
      if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        proxyType = createProxyClass(beanClass, beanName, specificInterceptors, targetSource);
        this.proxyTypes.put(cacheKey, proxyType);
      }
    }
    return (proxyType != null ? proxyType : beanClass);
  }

  @Override
  public Constructor<?> @Nullable [] determineCandidateConstructors(Class<?> beanClass, String beanName) {
    return null;
  }

  @Override
  public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyBeanReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);
  }

  /**
   * Create a proxy with the configured interceptors if the bean is
   * identified as one to proxy by the subclass.
   *
   * @see #getAdvicesAndAdvisorsForBean
   */
  @Override
  @Nullable
  public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyBeanReferences.remove(cacheKey) != bean) {
        return wrapIfNecessary(bean, beanName, cacheKey);
      }
    }
    return bean;
  }

  /**
   * Build a cache key for the given bean class and bean name.
   * <p>Note: this implementation does not return a concatenated
   * class/name String anymore but rather the most efficient cache key possible:
   * a plain bean name, prepended with {@link BeanFactory#FACTORY_BEAN_PREFIX}
   * in case of a {@code FactoryBean}; or if no bean name specified, then the
   * given bean {@code Class} as-is.
   *
   * @param beanClass the bean class
   * @param beanName the bean name
   * @return the cache key for the given class and name
   * @since 4.0
   */
  protected Object getCacheKey(Class<?> beanClass, @Nullable String beanName) {
    if (StringUtils.isNotEmpty(beanName)) {
      return FactoryBean.class.isAssignableFrom(beanClass)
              ? BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName;
    }
    else {
      return beanClass;
    }
  }

  /**
   * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
   *
   * @param bean the raw bean instance
   * @param beanName the name of the bean
   * @param cacheKey the cache key for metadata access
   * @return a proxy wrapping the bean, or the raw bean instance as-is
   */
  protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.isNotEmpty(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
    }
    Class<?> beanClass = bean.getClass();
    if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
      Object proxy = createProxy(
              beanClass, beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
  }

  protected TargetSource getTargetSource(Object bean, String def) {
    TargetSource targetSource = getCustomTargetSource(bean.getClass(), def);
    if (targetSource == null) {
      targetSource = new SingletonTargetSource(bean);
    }
    return targetSource;
  }

  /**
   * Create an AOP proxy for the given bean.
   *
   * @param beanClass the class of the bean
   * @param beanName the name of the bean
   * @param specificInterceptors the set of interceptors that is
   * specific to this bean (may be empty, but not null)
   * @param targetSource the TargetSource for the proxy,
   * already pre-configured to access the bean
   * @return the AOP proxy for the bean
   * @see #buildAdvisors
   */
  protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
          Object @Nullable [] specificInterceptors, TargetSource targetSource) {

    return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);
  }

  private Class<?> createProxyClass(Class<?> beanClass, @Nullable String beanName,
          Object @Nullable [] specificInterceptors, TargetSource targetSource) {

    return (Class<?>) buildProxy(beanClass, beanName, specificInterceptors, targetSource, true);
  }

  private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
          Object @Nullable [] specificInterceptors, TargetSource targetSource, boolean classOnly) {

    if (beanFactory instanceof ConfigurableBeanFactory cbf) {
      AutoProxyUtils.exposeTargetClass(cbf, beanName, beanClass);
    }

    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
    proxyFactory.setFrozen(false);

    if (shouldProxyTargetClass(beanClass, beanName)) {
      proxyFactory.setProxyTargetClass(true);
    }
    else {
      Class<?>[] ifcs = this.beanFactory instanceof ConfigurableBeanFactory cbf
              ? AutoProxyUtils.determineExposedInterfaces(cbf, beanName) : null;
      if (ifcs != null) {
        proxyFactory.setProxyTargetClass(false);
        for (Class<?> ifc : ifcs) {
          proxyFactory.addInterface(ifc);
        }
      }
      if (ifcs != null ? ifcs.length == 0 : !proxyFactory.isProxyTargetClass()) {
        evaluateProxyInterfaces(beanClass, proxyFactory);
      }
    }

    if (proxyFactory.isProxyTargetClass()) {
      // Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
      if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
        // Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
        for (Class<?> ifc : beanClass.getInterfaces()) {
          proxyFactory.addInterface(ifc);
        }
      }
    }

    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(isFrozen());
    if (advisorsPreFiltered()) {
      proxyFactory.setPreFiltered(true);
    }

    // Use original ClassLoader if bean class not locally loaded in overriding class loader
    ClassLoader classLoader = getProxyClassLoader();
    if (classLoader instanceof SmartClassLoader scl && classLoader != beanClass.getClassLoader()) {
      classLoader = scl.getOriginalClassLoader();
    }
    return classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader);
  }

  /**
   * Subclasses should override this method to return {@code true} if the
   * given bean should not be considered for auto-proxying by this post-processor.
   * <p>Sometimes we need to be able to avoid this happening, for example, if it will lead to
   * a circular reference or if the existing target instance needs to be preserved.
   * This implementation returns {@code false} unless the bean name indicates an
   * "original instance" according to {@code AutowireCapableBeanFactory} conventions.
   *
   * @param beanClass the class of the bean
   * @param beanName the name of the bean
   * @return whether to skip the given bean
   * @see AutowireCapableBeanFactory#ORIGINAL_INSTANCE_SUFFIX
   */
  protected boolean shouldSkip(Class<?> beanClass, String beanName) {
    return AutoProxyUtils.isOriginalInstance(beanName, beanClass);
  }

  /**
   * Return whether the given bean class represents an infrastructure class
   * that should never be proxied.
   * <p>The default implementation considers Advices, Advisors and
   * AopInfrastructureBeans as infrastructure classes.
   *
   * @param beanClass the class of the bean
   * @return whether the bean represents an infrastructure class
   * @see org.aopalliance.aop.Advice
   * @see infra.aop.Advisor
   * @see infra.aop.AopInfrastructureBean
   */
  protected boolean isInfrastructureClass(Class<?> beanClass) {
    boolean retVal = Advice.class.isAssignableFrom(beanClass)
            || Advisor.class.isAssignableFrom(beanClass)
            || Pointcut.class.isAssignableFrom(beanClass)
            || AopInfrastructureBean.class.isAssignableFrom(beanClass);
    if (retVal && log.isTraceEnabled()) {
      log.trace("Did not attempt to auto-proxy infrastructure class [{}]", beanClass.getName());
    }
    return retVal;
  }

  /**
   * Determine whether the given bean should be proxied with its target class rather than its interfaces.
   * <p>Checks the {@link AutoProxyUtils#PRESERVE_TARGET_CLASS_ATTRIBUTE "preserveTargetClass" attribute}
   * of the corresponding bean definition.
   *
   * @param beanClass the class of the bean
   * @param beanName the name of the bean
   * @return whether the given bean should be proxied with its target class
   * @see AutoProxyUtils#shouldProxyTargetClass
   */
  protected boolean shouldProxyTargetClass(Class<?> beanClass, @Nullable String beanName) {
    return this.beanFactory instanceof ConfigurableBeanFactory cbf
            && AutoProxyUtils.shouldProxyTargetClass(cbf, beanName);
  }

  /**
   * Return whether the Advisors returned by the subclass are pre-filtered
   * to match the bean's target class already, allowing the ClassFilter check
   * to be skipped when building advisors chains for AOP invocations.
   * <p>Default is {@code false}. Subclasses may override this if they
   * will always return pre-filtered Advisors.
   *
   * @return whether the Advisors are pre-filtered
   * @see #getAdvicesAndAdvisorsForBean
   * @see Advised#setPreFiltered
   */
  protected boolean advisorsPreFiltered() {
    return false;
  }

  /**
   * Determine the advisors for the given bean, including the specific interceptors
   * as well as the common interceptor, all adapted to the Advisor interface.
   *
   * @param beanName the name of the bean
   * @param specificInterceptors the set of interceptors that is
   * specific to this bean (may be empty, but not null)
   * @return the array of Advisors for the given bean
   */
  protected Advisor[] buildAdvisors(@Nullable String beanName, Object @Nullable [] specificInterceptors) {
    // Handle prototypes correctly...
    Advisor[] commonInterceptors = resolveInterceptorNames();

    ArrayList<Object> allInterceptors = new ArrayList<>();
    if (specificInterceptors != null) {
      if (specificInterceptors.length > 0) {
        // specificInterceptors may equal PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS
        allInterceptors.addAll(Arrays.asList(specificInterceptors));
      }
      if (commonInterceptors.length > 0) {
        if (this.applyCommonInterceptorsFirst) {
          allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
        }
        else {
          CollectionUtils.addAll(allInterceptors, commonInterceptors);
        }
      }
    }
    if (log.isTraceEnabled()) {
      int nrOfCommonInterceptors = commonInterceptors.length;
      int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
      log.trace("Creating implicit proxy for bean '{}' with {} common interceptors and {} specific interceptors",
              beanName, nrOfCommonInterceptors, nrOfSpecificInterceptors);
    }

    Advisor[] advisors = new Advisor[allInterceptors.size()];
    for (int i = 0; i < allInterceptors.size(); i++) {
      advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
    }
    return advisors;
  }

  /**
   * Resolves the specified interceptor names to Advisor objects.
   *
   * @see #setInterceptorNames
   */
  @SuppressWarnings("NullAway")
  private Advisor[] resolveInterceptorNames() {
    BeanFactory bf = this.beanFactory;
    ConfigurableBeanFactory cbf = (bf instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) bf : null);
    ArrayList<Advisor> advisors = new ArrayList<>();
    for (String beanName : interceptorNames) {
      if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) {
        Assert.state(bf != null, "BeanFactory required for resolving interceptor names");
        Object next = bf.getBean(beanName);
        advisors.add(this.advisorAdapterRegistry.wrap(next));
      }
    }
    return advisors.toArray(new Advisor[0]);
  }

  /**
   * Subclasses may choose to implement this: for example,
   * to change the interfaces exposed.
   * <p>The default implementation is empty.
   *
   * @param proxyFactory a ProxyFactory that is already configured with
   * TargetSource and interfaces and will be used to create the proxy
   * immediately after this method returns
   */
  protected void customizeProxyFactory(ProxyFactory proxyFactory) {
  }

  /**
   * Return whether the given bean is to be proxied, what additional
   * advices (e.g. AOP Alliance interceptors) and advisors to apply.
   *
   * @param beanClass the class of the bean to advise
   * @param beanName the name of the bean
   * @param targetSource the TargetSource returned by the
   * {@link #getCustomTargetSource} method: may be ignored.
   * Will be {@code null} if no custom target source is in use.
   * @return an array of additional interceptors for the particular bean;
   * or an empty array if no additional interceptors but just the common ones;
   * or {@code null} if no proxy at all, not even with the common interceptors.
   * See constants DO_NOT_PROXY and PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS.
   * @throws BeansException in case of errors
   * @see #DO_NOT_PROXY
   * @see #PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS
   */
  protected abstract Object @Nullable [] getAdvicesAndAdvisorsForBean(
          Class<?> beanClass, String beanName, @Nullable TargetSource targetSource);

}
